package com.example.flowabledemo.service.impl;

import cn.hutool.core.date.BetweenFormatter.Level;
import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.img.ImgUtil;
import com.example.flowabledemo.common.HttpCode;
import com.example.flowabledemo.common.flowable.ProcessValues;
import com.example.flowabledemo.data.dto.ResponsePageResult;
import com.example.flowabledemo.data.dto.ResponseResult;
import com.example.flowabledemo.data.entity.AaApproval;
import com.example.flowabledemo.data.entity.AaProcess;
import com.example.flowabledemo.data.entity.AaUser;
import com.example.flowabledemo.data.mapper.AaApprovalMapper;
import com.example.flowabledemo.data.mapper.AaProcessMapper;
import com.example.flowabledemo.data.mapper.FlowableMapper;
import com.example.flowabledemo.data.pojo.FlowableMap;
import com.example.flowabledemo.data.pojo.SecUser;
import com.example.flowabledemo.data.vo.AapprovalHistoryVo;
import com.example.flowabledemo.data.vo.ApplyLeaveVo;
import com.example.flowabledemo.data.vo.ApprovalLeaveVo;
import com.example.flowabledemo.data.vo.HistoryProcessVo;
import com.example.flowabledemo.data.vo.ProcessVo;
import com.example.flowabledemo.data.vo.UserInfoVo;
import com.example.flowabledemo.service.FlowableService;
import com.example.flowabledemo.service.UserService;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import java.awt.Color;
import java.awt.Font;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.engine.HistoryService;
import org.flowable.engine.ProcessEngine;
import org.flowable.engine.ProcessEngineConfiguration;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.TaskService;
import org.flowable.engine.history.HistoricActivityInstance;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.engine.task.Comment;
import org.flowable.image.ProcessDiagramGenerator;
import org.flowable.task.api.Task;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * @author linke
 * @date 2020/11/27 20:14
 * @description
 */
@Service
@Slf4j
public class FlowableServiceImpl implements FlowableService {

  @Autowired
  private RuntimeService runtimeService;
  @Autowired
  private TaskService taskService;
  @Autowired
  private RepositoryService repositoryService;
  @Autowired
  private HistoryService historyService;
  @Autowired
  @Qualifier("processEngine")
  private ProcessEngine processEngine;
  @Autowired
  private AaProcessMapper aaProcessMapper;
  @Autowired
  private UserService userService;
  @Autowired
  private FlowableMapper flowableMapper;
  @Autowired
  private AaApprovalMapper aaApprovalMapper;

  @Override
  public ProcessInstance start(String processKey, String businessKey, Map<String, Object> map) {
    return runtimeService.startProcessInstanceByKey(processKey, businessKey, map);
  }

  @Override
  public void complete(String taskId, Map<String, Object> map) {
    taskService.complete(taskId, map);
  }

  @Override
  public Task getNewestTask(String processInstanceId) {
    return taskService.createTaskQuery().processInstanceId(processInstanceId).singleResult();
  }

  @Override
  public void updateProcessStatus(String processInstanceId, String processStatus) {
    aaProcessMapper.updateStatus(processInstanceId, processStatus);
  }

  @Override
  public HistoricActivityInstance getLastHistoryData(String processInstanceId) {
    List<HistoricActivityInstance> historys =
        historyService.createHistoricActivityInstanceQuery()
            .processInstanceId(processInstanceId)
            .orderByHistoricActivityInstanceId().desc()
            .listPage(0, 1);
    if (historys.isEmpty()) {
      return null;
    }
    return historys.get(0);
  }

  @Override
  public void createProcessImage(String processInstanceId, HttpServletResponse response) throws Exception {
    String processDefinitionId = null;
    ProcessInstance pi = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
    //获取历史流程
    List<HistoricActivityInstance> historyProcess = historyService // 历史相关Service
        .createHistoricActivityInstanceQuery() // 创建历史活动实例查询
        .processInstanceId(processInstanceId) // 执行流程实例id
        //.finished()
        .list();
    if (pi != null) {
      processDefinitionId = pi.getProcessDefinitionId();
    } else {
      //从历史中找
      if (!historyProcess.isEmpty()) {
        processDefinitionId = historyProcess.get(0).getProcessDefinitionId();
      } else {
        //response.setContentType("text/html; charset=utf-8");
        // response.getWriter().write("<html><head></head><body>此流程已经完结或者已经被删除</body></html>");
        //手动生成一张提示语图片
        generateWordsImage("此流程已经失效或者已经被删除", response);
        return;
      }
    }

    List<String> activityIds = new ArrayList<>();
    List<String> flows = new ArrayList<>();
    //获取流程图
    BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinitionId);
    for (HistoricActivityInstance hi : historyProcess) {
      String activityType = hi.getActivityType();
      if (activityType.equals("sequenceFlow") || activityType.equals("exclusiveGateway")) {
        flows.add(hi.getActivityId());
      } else if (activityType.equals("userTask") || activityType.equals("startEvent")) {
        activityIds.add(hi.getActivityId());
      }
    }
    List<Task> tasks = taskService.createTaskQuery().processInstanceId(processInstanceId).list();
    for (Task task : tasks) {
      activityIds.add(task.getTaskDefinitionKey());
    }
    ProcessEngineConfiguration engConf = processEngine.getProcessEngineConfiguration();
    //定义流程画布生成器
    ProcessDiagramGenerator processDiagramGenerator = engConf.getProcessDiagramGenerator();
    InputStream in = processDiagramGenerator.generateDiagram(bpmnModel, "png", activityIds, flows,
        engConf.getActivityFontName(), engConf.getLabelFontName(), engConf.getAnnotationFontName(),
        engConf.getClassLoader(), 2.0, true);

    OutputStream out = null;
    byte[] buf = new byte[1024];
    int legth = 0;
    try {
      response.setContentType("image/png");
      out = response.getOutputStream();
      while ((legth = in.read(buf)) != -1) {
        out.write(buf, 0, legth);
      }
    } finally {
      if (in != null) {
        in.close();
      }
      if (out != null) {
        out.close();
      }
    }

  }

  private void generateWordsImage(String words, HttpServletResponse response) throws Exception {

    Font font = new Font("微软雅黑", Font.BOLD, 30);
    BufferedImage image = ImgUtil.createImage(words, font, Color.white, Color.red, BufferedImage.TYPE_INT_RGB);

    /*int width = 450; // 图片宽
    int height = 300;// 图片高
    // 得到图片缓冲区
    BufferedImage bi = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);// INT精确度达到一定,RGB三原色，高度70,宽度150
    Graphics2D g2 = (Graphics2D) bi.getGraphics();
    g2.setColor(Color.WHITE); // 设置背景颜色
    g2.fillRect(0, 0, width, height);// 填充整张图片(其实就是设置背景颜色)
    g2.setColor(Color.black);// 设置字体颜色
    //g2.setStroke(new BasicStroke(2.0f)); // 边框加粗
    //g2.drawRect(1, 1, width - 2, height - 2); // 画边框就是黑边框
    // 设置标题的字体,字号,大小
    Font titleFont = new Font("宋体", Font.BOLD, 24);
    g2.setFont(titleFont);
    g2.drawString(words, 34, 140);*/

    ByteArrayOutputStream os = new ByteArrayOutputStream();
    ImageIO.write(image, "png", os);
    InputStream input = new ByteArrayInputStream(os.toByteArray());
    OutputStream out = null;
    byte[] buf = new byte[1024];
    int legth = 0;
    try {
      response.setContentType("image/png");
      out = response.getOutputStream();
      while ((legth = input.read(buf)) != -1) {
        out.write(buf, 0, legth);
      }
    } finally {
      if (input != null) {
        input.close();
      }
      if (out != null) {
        out.close();
      }
      if (os != null) {
        os.close();
      }
    }
  }

  @Override
  public ResponsePageResult getProcess(String status, Integer page, Integer limit) {
    SecUser user = userService.getUser();
    Page helper = PageHelper.startPage(page, limit);
    List<ProcessVo> list = aaProcessMapper.getProcess(status, user.getUsername());
    return new ResponsePageResult(HttpCode.OK, page, limit, helper.getTotal(), list);
  }

  @Override
  public AaProcess getProcess(String processInstanceId) {
    return aaProcessMapper.get(processInstanceId);
  }

  @Override
  public List<HistoryProcessVo> getHistory(String processInstanceId) {
    List<HistoryProcessVo> res = flowableMapper.getHistory(processInstanceId);
    List<HistoryProcessVo> vos = new ArrayList<>();
    res.forEach(r -> {
      long between;
      if (r.getEndTime() != null) {
        between = r.getEndTime().getTime() - r.getStartTime().getTime();
      } else {
        between = new Date().getTime() - r.getStartTime().getTime();
      }
      r.setHour(DateUtil.formatBetween(between, Level.SECOND));
      vos.add(r);
    });
    return vos;
  }


  @Override
  public Comment addCommand(String taskId, String processInstanceId, String message) {
    return taskService.addComment(taskId, processInstanceId, message);
  }

  @Override
  public ResponsePageResult getApproval(String status, Integer page, Integer limit) {
    SecUser user = userService.getUser();
    Page helper = PageHelper.startPage(page, limit);
    List<ProcessVo> list = aaProcessMapper.getApproval(status, user.getDepartmentCode(), user.getUsername());
    return new ResponsePageResult(HttpCode.OK, page, limit, helper.getTotal(), list);
  }

  @Override
  public ResponsePageResult getApprovalHistory(String status, Integer page, Integer limit) {
    SecUser user = userService.getUser();
    Page helper = PageHelper.startPage(page, limit);
    List<AapprovalHistoryVo> list = aaProcessMapper.getApprovalHistory(status, user.getDepartmentCode(), user.getUsername());
    return new ResponsePageResult(HttpCode.OK, page, limit, helper.getTotal(), list);
  }

  @Override
  public ApprovalLeaveVo getApprovalLeaveVo(String processInstanceId) {
    //{userAction=1, handleUser=null, endDate=2020-12-03 00:00:00, handleGroup=yfb, description=阿萨德撒大, type=leaveProcesses,
    // title=看看, startDate=2020-12-02 00:00:00}
    Map<String, String> map = this.getMap(processInstanceId);
    ApprovalLeaveVo vo = new ApprovalLeaveVo();
    vo.setType(map.get("type"));
    vo.setStartDate(map.get("startDate"));
    vo.setEndDate(map.get("endDate"));
    vo.setTitle(map.get("title"));
    vo.setDescription(map.get("description"));

    AaProcess process = this.getAaProcess(processInstanceId);
    UserInfoVo userInfoVo = userService.getUserInfoVo(process.getUsername());
    vo.setName(userInfoVo.getName());
    vo.setUsername(userInfoVo.getUsername());
    vo.setJobName(userInfoVo.getJobName());
    vo.setDepartmentCode(userInfoVo.getDepartmentCode());
    vo.setDepartmentName(userInfoVo.getDepartmentName());

    Date start = DateUtil.parse(vo.getStartDate());
    Date end = DateUtil.parse(vo.getEndDate());

    long ms = DateUtil.between(start, end, DateUnit.MS);
    //Level.MINUTE表示精确到分
    String dateBetween = DateUtil.formatBetween(ms, Level.MINUTE);
    vo.setDateBetween(dateBetween);
    return vo;
  }

  @Override
  public AaProcess getAaProcess(String processInstanceId) {
    return aaProcessMapper.get(processInstanceId);
  }

  @Override
  public ApplyLeaveVo getApplyLeaveVo(String processInstanceId) {
    Map<String, String> map = this.getMap(processInstanceId);
    ApplyLeaveVo vo = new ApplyLeaveVo();
    vo.setType(map.get("type"));
    vo.setStartDate(map.get("startDate"));
    vo.setEndDate(map.get("endDate"));
    vo.setTitle(map.get("title"));
    vo.setDescription(map.get("description"));
    return vo;
  }

  @Override
  public Map<String, String> getMap(String processInstanceId) {
    List<FlowableMap> maps = flowableMapper.getMap(processInstanceId);
    if (maps.isEmpty()) {
      return null;
    }
    Map<String, String> map = new HashMap<>();
    maps.forEach(m -> {
      map.put(m.getK(), m.getV());
    });
    return map;
  }

  @Override
  public void updateMap(String processInstanceId, Map<String, Object> map) {
    if (map.isEmpty()) {
      return;
    }
    List<FlowableMap> list = new ArrayList<>();
    for (Map.Entry<String, Object> entry : map.entrySet()) {
      Object v = entry.getValue();
      flowableMapper.updateMap(processInstanceId, entry.getKey(), v == null ? null : String.valueOf(v));
    }
  }

  @Override
  public void addApproval(AaApproval aaApproval) {
    aaApprovalMapper.insertSelective(aaApproval);
  }

  @Override
  public void addProcess(AaProcess aaProcess) {
    aaProcessMapper.insertSelective(aaProcess);
  }

  @Override
  public int getRunProcessNumber(String username, String processType) {
    return aaProcessMapper.getRunProcessNumber(username, processType);
  }

  @Transactional
  @Override
  public ResponseResult cancelProcess(String username, String processInstanceId, String reason) {
    AaUser user = userService.getUser(username);
    AaProcess aaProcess = this.getAaProcess(processInstanceId);
    if (aaProcess != null && !aaProcess.getUsername().equals(user.getUsername())) {
      return new ResponseResult(HttpCode.FAIL, "插销失败，该流程不存在！");
    }
    Task task = this.getNewestTask(processInstanceId);
    AaApproval aaApproval = new AaApproval();
    aaApproval.setDepartmentCode(user.getDepartmentCode());
    aaApproval.setTaskId(task.getId());
    aaApproval.setCreateTime(new Date());
    aaApproval.setStatus(ProcessValues.APPROVAL_STATUS_CANCEL);
    aaApproval.setUsername(user.getUsername());
    aaApproval.setRemarks(reason);
    aaApproval.setProcessId(processInstanceId);
    //1、添加撤销记录
    this.addApproval(aaApproval);
    //2、修改流程状态:-1撤销删除
    this.updateProcessStatus(processInstanceId, ProcessValues.PROCESS_STATUS_CANCEL);
    //3、添加备注
    taskService.addComment(task.getId(), processInstanceId, reason);
    //4、删除任务
    taskService.delegateTask(task.getId(), reason);
    return new ResponseResult(HttpCode.OK, "撤销成功！");
  }
}
